#ifdef WIN32
	#include <GL/GLee.h>
#endif

#include "Water.h"
#include <time.h>

#include "../stdafx.h"
#include "../helper/Helper.h"
#include "../math/Math.h"
#include "../camera/Camera.h"
#include "../Global.h"
#include "../shader/CgShader.h"
#include "../light/Light.h"

#include <opencv/cv.h>
#include <opencv/highgui.h>

Shader* waterShader;
/* light */
Light waterLight;

Water::Water() : scaleX(20.0), 
				 //scaleY(0.15f), /* sin */
				 scaleY(0.5f),
				 downY(10.0f), 
				 offset(0), 
				 sinWave(0.0f), 
				 sinCoefficient(0.0f), 
				 perlin(0),
				 perlinHeightmap(0),
				 numVertice(0)
				 
{
	/* Load textures */
	textures = new GLuint[TEXTURE_WATER_COUNT];
	LoadWater();
	
	/* Camera */
	camera = Global::GetGlobal()->GetCamera();
	
	/* allocate new memory for heightmap */
	perlinHeightmap = new GLubyte[width*height];
	
	/* Init Perlin noise */
	perlin = new PerlinNoise;
	
	/* Create initial heightmap */
	CreatePerlinHeightmap3d(0);
	
	/* === test === */
	SeparatePerlinHeightmap();	
	
	/* load initial vertice */
	LoadVertice();
	
	/* Water shader */
	waterShader = new CgShader;	
	waterShader->LoadVertex("shader/water.vp", "matViewProj", "matTransform", "#");
	//waterShader->LoadVertex("shader/simple_vertex.vp");
	//waterShader->LoadVertex("shader/green.vp");
	waterShader->LoadPixel("shader/water.fp", /* param */
						"@texture",
						"eyePosition", 
						"lightPosition", 			
						"lightColor", 
						"materialAmbient", 
						"materialDiffuse", 
						"materialSpecular", 
						"shininess", 
						"materialColor",
						"#" //end sign
						);
		
	/* light */
	waterLight.pos = Vector3f(0, 50, -100);
	waterLight.ambient = Vector3f(1, 1, 1);
	waterLight.diffuse = Vector3f(0.8, 0.9, 0.85);
	waterLight.specular = Vector3f(1, 1, 1);
	
	/* water material */
	this->material = Material(0.1, 0.5, 0.9, 21);
	this->material.color = Vector3f(0.9f, 0.9f, 1.0f);
	this->material.reflection = 0.0f;
	
}

Water::~Water()
{
	ReleaseNewArray(textures);	
	ReleaseNewArray(perlinHeightmap);
	ReleaseNew(perlin);
}

//------------------------------------------------------------
// Load vertice after each Perlin Noise update
//------------------------------------------------------------
void Water::LoadVertice() {
	int width2 = waterWidth / 2;
	int height2 = waterHeight / 2;
	int h = 0;
	
	/* n is the number of vertices along each size of the terrain
	   This triangle strip uses n * (n + n - 2) or (n - 1) * n * 2 approximates to 2*n*n vertices
	*/
	
	if (numVertice == 0) {
		numVertice = waterWidth*waterHeight;	
		vertice = new Vertex[numVertice];
	}
	
	Vertex* v = vertice;
	int z = 0, x = 0;
	for (z = 0; z < waterHeight; z++) {
		for (x = 0; x < waterWidth; x++, v++) {
			float coordX = (x - width2) * scaleX;					
			float coordZ = (z - height2) * scaleX; //from back to front as the orientation of the heightmap image	
			
			//(!) Ensure that two consecutive texture mapping point do not loop
			//int zTexture = (z + offset) % (height);		
			int zTexture = (z) % (height);
			if (zTexture == height-1) zTexture = 0;
			
			int zPerlin = z % height;
			int xPerlin = x % width;			
			h = perlinHeightmap[zPerlin*width+xPerlin];			   
			v->position.Assign(coordX, h * scaleY + downY, coordZ);
			//v->normal.Assign(0, 1, 0);
			//v->texture.u = (float)x / (width-1);
			//v->texture.v = (float)zTexture / (height-1);
			//v->texture.v = (float)z / (height-1);
			v->texture.u = (float)x / (waterWidth-1);
			v->texture.v = (float)z / (waterHeight-1);
		}
	}
	
	/* normal computation */
	Vertex* pHere = vertice, *pRight = pHere+1, *pBottom = pHere+waterWidth, *pDiagonal = pBottom+1;
	Vector3f v1;
	Vector3f v2;
	Vector3f cross;
	Vertex* p = vertice;
	for (z = 0; z < waterHeight; z++) {
		for (x = 0; x < waterWidth; x++, p++) {
			p->normal.Assign(0, 0, 0);
			p->tag = 0;
		}
	}
	for (z = 0; z < waterHeight - 1; z++) {
		for (x = 0; x < waterWidth - 1; x++) {
			/* first triangle */
			v1 = pBottom->position - pHere->position;
			v2 = pRight->position - pHere->position;
			cross = v1.Cross(v2);
			pHere->normal += cross;
			pRight->normal += cross;
			pBottom->normal += cross;
			pHere->tag++;
			pRight->tag++;
			pBottom->tag++;
			
			/* second triangle */
			v1 = pRight->position - pDiagonal->position;
			v2 = pBottom->position - pDiagonal->position;			
			cross = v1.Cross(v2);
			pDiagonal->normal += cross;
			pRight->normal += cross;
			pBottom->normal += cross;
			pDiagonal->tag++;
			pRight->tag++;
			pBottom->tag++;			
			
			//next column
			pHere++;
			pRight++;
			pBottom++;
			pDiagonal++;
		}
		
		//jump to next row
		pHere++;
		pRight++;
		pBottom++;
		pDiagonal++;
	}
	
	pHere = vertice;
	for (z = 0; z < waterHeight; z++) {
		for (x = 0; x < waterWidth; x++, pHere++) {
			pHere->normal /= pHere->tag;			
		}
	}	
}

void Water::LoadWater() {
	glGenTextures(TEXTURE_WATER_COUNT, textures);
		
	glBindTexture(GL_TEXTURE_2D, textures[0]);
	SDL_Surface* water = Helper::LoadTextureImage("texture/water/water16.jpg");
	
	GLint iWidth = width = water->w;
	GLint iHeight = height = water->h;
	GLenum eFormat = GL_RGB;
	glTexImage2D(GL_TEXTURE_2D, 0 /* mipmap */, GL_RGB /* internal image format */, iWidth, iHeight, 0, eFormat, GL_UNSIGNED_BYTE, water->pixels);
		
	//Texture setup
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	
	//cleanup
	delete water;
	
	/* assign water size */
	waterWidth = width * 3;
	waterHeight = height * 3;
}

long curFrame = 0;
int maxFrames = 256;
float timeElapsed = 0;
float maxTimeElapsed = 1024*8;
float noiseTime;
int dir = 1;
void Water::Update(long time) {
	offset += 0.01f * time;
	if (offset > height-1) offset -= height-1;
	
	sinCoefficient = 0.001f;
	sinWave += sinCoefficient * time;
	if (sinWave > M_PI*2) sinWave -= 2*M_PI;
	
	curFrame = (curFrame + dir) % maxFrames ;
	timeElapsed += 0.5f * time;
	if (timeElapsed > maxTimeElapsed)
		timeElapsed -= maxTimeElapsed;
	
	//if (curFrame < 0) {dir = 1; curFrame = 0;}
	//else if (curFrame > gradientSize) {dir = -1; curFrame = gradientSize;}
	noiseTime = (float)curFrame / (maxFrames-1);
	noiseTime = (float) timeElapsed / maxTimeElapsed;
	//curFrame++;
	CreatePerlinHeightmap3d(noiseTime);
	LoadVertice();  //reload vertice
	
	waterLight.pos.Assign(camera->GetEye());
}



void Water::RenderPerlinNoise() {	
	camera->Begin2dMode();
		
	//Draw, remember to disable textures
	glPushAttrib(GL_ENABLE_BIT); //disable texture
		glDisable(GL_TEXTURE_2D);
		glDisable(GL_DEPTH_TEST);				
		
		glRasterPos2i(256*3+1, camera->GetWindowHeight());		
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);	
		glPixelZoom(1, -1);		
		glDrawPixels(width, height, GL_LUMINANCE, GL_UNSIGNED_BYTE, perlinHeightmap);
		
	glPopAttrib();
	
	//End 2d mode
	camera->End2dMode();	
}

void Water::RenderWater() {
	glEnable(GL_CULL_FACE);
	//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	//BeginAlignPosition();
	//any more scales?
	glPushAttrib(GL_ENABLE_BIT);
		/* disable depth test, enable blend and draw from back to front */
#ifdef WIN32
	if (GLEE_VERSION_1_2)          //is GL1.2 available?
	{
		glBlendColor(0.6f, 0.6f, 0.6f, 1.0f);
	}
#else
		glBlendColor(0.6f, 0.6f, 0.6f, 1.0f);		
#endif
		glBlendFunc(GL_CONSTANT_COLOR, GL_ONE_MINUS_CONSTANT_COLOR);
		glEnable(GL_BLEND);
		//glDisable(GL_BLEND);		
		//glDisable(GL_DEPTH_TEST);
		glEnable(GL_DEPTH_TEST);
		
		glColor3f(1.0f, 1.0f, 1.0f);		
		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, textures[0]);
		
	float matTransform[16];
	Math::Translate(this->pos, matTransform);
	float* matViewProj = camera->GetViewProjectionMatrix();
	waterShader->Begin();		
	waterShader->SetMatrixParamfc("matViewProj", matViewProj);
	waterShader->SetMatrixParamfc("matTransform", matTransform);
	waterShader->SetTextureParam("texture", textures[0]);
	//printf("Begin set some param\n");
	float f[3];
	//eye position
	camera->GetEye().ToFloat3(f);
	waterShader->SetParamPixel3fv("eyePosition", f);
	
	//light position
	//waterLight.pos.ToFloat3(f);
	// make eye as light source
	camera->GetEye().ToFloat3(f);
	//Math::TransformVector(matViewProj, f, f);		
	waterShader->SetParamPixel3fv("lightPosition", f);
			
	//light color, take diffuse as its main color
	waterLight.diffuse.ToFloat3(f);
	waterShader->SetParamPixel3fv("lightColor", f);
	
	//material
	material.color.ToFloat3(f);
	waterShader->SetParamPixel3fv("materialColor", f);
	waterShader->SetParamPixel1fv("materialAmbient", material.ambient);
	waterShader->SetParamPixel1fv("materialDiffuse", material.diffuse);
	waterShader->SetParamPixel1fv("materialSpecular", material.specular);
	waterShader->SetParamPixel1dv("shininess", material.phongShininess);
	waterShader->Update();
				
		Vertex* p1 = vertice; //first row
		Vertex* p2 = p1 + waterWidth; //second row
		for (int z = 0; z < waterHeight - 1; z++) {
			glBegin(GL_TRIANGLE_STRIP); //start of a row of triangle strip
			for (int x = 0; x < waterWidth; x++) {
				glNormal3f(p1->normal.x, p1->normal.y, p1->normal.z);				
				glTexCoord2f(p1->texture.u, p1->texture.v);
				glVertex3f(p1->position.x, p1->position.y, p1->position.z);
				p1++;
				
				glNormal3f(p2->normal.x, p2->normal.y, p2->normal.z);
				glTexCoord2f(p2->texture.u, p2->texture.v);
				glVertex3f(p2->position.x, p2->position.y, p2->position.z);
				p2++;
			}
			glEnd();
		}
	
	waterShader->End();	
		
	glPopAttrib();
	
	//EndAlignPosition();
}

void Water::GetSize(float &fWidth, float &fHeight) {
	//256 pixels of height map -> 255 quads per line
	fWidth = (width-1) * scaleX;
	fHeight = (height-1) * scaleX;
}

void Water::RenderInfinite() {
	float fWidth, fHeight;
	this->GetSize(fWidth, fHeight);
	
	Vector3f oldPos = pos;
	//from eye point in a terrain, draw 8 terrains around it	
	int i = 0, j = 0;
	for (i = -1; i <= 1; i++) 
	{
		for (j = -1; j <= 1; j++) 
		{
			this->pos = oldPos + Vector3f(fWidth * j, 0, fHeight * i);
			this->RenderWater();
		}
	}
	
	//restore
	pos = oldPos;
}

void Water::Render() {
	//RenderInfinite();
	RenderWater();
}

/* =============== PERLIN NOISE ==================== */
void Water::CreatePerlinHeightmap2d() {
	/* fill heightmap with values from perlin noise function */
	int x, z;
	int index = 0;
	for (z = 0; z < height; z++)
		for (x = 0; x < width; x++, index++)
			perlinHeightmap[index] = perlin->PerlinNoise2d(x, z, width, height);
}

void Water::CreatePerlinHeightmap3d(float time) {
	/* fill heightmap with values from perlin noise function */
	int x, z;
	/*for (z = 0; z < height; z++)		
		perlinHeightmap[z*width+0] = perlinHeightmap[z*width+(width-1)] = PerlinNoise3d(0, z, time);
	
	for (x = 0; x < width; x++)		
		perlinHeightmap[0*width+x] = perlinHeightmap[(height-1)*width+x] = PerlinNoise3d(x, 0, time);
		
	for (z = 1; z < height-1; z++)
		for (x = 1; x < width-1; x++)
			perlinHeightmap[z*width+x] = PerlinNoise3d(x, z, time);*/
	for (z = 0; z < height; z++)
		for (x = 0; x < width; x++)
			perlinHeightmap[z*width+x] = perlin->PerlinNoise3d(x, z, time, width, height);
}


void Water::SeparatePerlinHeightmap() {
	float total = 0;	
	int f = 1; //frequency
	float a = 1; //amplitude of each harmonic
	int rankCount = perlin->GetRankCount();
	for (int i = 0; i < rankCount; i++) {//the number of harmonics		
		for (int x = 0; x < width; x++) {			
			/* 1D test */
			total = perlin->PerlinNoise1d((float)x  / (width-1) * f, f) * a;
			total = (total + 1.0f) * 255 / 2;			
			perlinHeightmap[x] = (GLubyte)total;
			
		}		
		/* save */
		SavePerlinHeightmap(1, i);
		
		f *= 2;
		//a /= 2;
	}
	
	total = 0;
	f = 1;
	a = 1;
	for (int i = 0; i < rankCount; i++) {//the number of harmonics
		for (int z = 0; z < height; z++) {		
			for (int x = 0; x < width; x++) {
				/* 2D test */
				//total = perlin->PerlinNoise2d((float)x / (width-1) * f, (float) z / (height-1) * f, f) * a;
				/* 3D test */
				total = perlin->PerlinNoise3d((float)x / (width-1) * f, (float) z / (height-1) * f, 0.0f, f) * a;
				total = (total + 1.0f) * 255 / 2;			
				perlinHeightmap[z*width+x] = (GLubyte)total;
			}		
		}
		/* save in 2D image*/
		SavePerlinHeightmap(2, i);
		
		f *= 2;
		//a /= 2;
	}
	
	/* combination */
	for (int z = 0; z < height; z++) {		
		for (int x = 0; x < width; x++) {
			/* 2D test */
			//perlinHeightmap[z*width+x] = perlin->PerlinNoise2d(x, z, width, height);
			/* 3D test */
			perlinHeightmap[z*width+x] = perlin->PerlinNoise3d(x, z, 0.9, width, height);
		}		
	}
	
	SavePerlinHeightmap(2, 1307);
	
}

void Water::SavePerlinHeightmap(int dimension, int i) {	
	char file[100];
	switch(dimension) {		
	case 1:
		sprintf(file, "perlin/%d.dat", i);
		FILE* f;
		f = fopen(file, "w");
		for (int x = 0; x < width; x++)
			fprintf(f, "%d %d\n", x, perlinHeightmap[x]);
		fclose(f);
		break;
	case 2:
		sprintf(file, "perlin/%d.bmp", i);
		
		IplImage* img = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 1);
		img->imageData = (char*)perlinHeightmap;
		cvSaveImage(file, img);
		cvReleaseImage(&img);
		break;
	}
}
